This one just worked

Problem
Code

fn energize(map: &[&[u8]], start: ((i32, i32), (i32, i32))) -> usize {
    let mut seen: HashMap<(i32, i32), Vec<(i32, i32)>> = HashMap::new();
    let mut rays = vec![start];

    while let Some(((mut x, mut y), (mut dx, mut dy))) = rays.pop() {
        while (0..map[0].len() as i32).contains(&x) && (0..map.len() as i32).contains(&y) {
	        // No need to repeat a pos+dir we've already seen.
            let entry = seen.entry((x, y)).or_default();
            if entry.contains(&(dx, dy)) {
                break;
            } else {
                entry.push((dx, dy));
            }

            match (map[y as usize][x as usize], dx, dy) {
                (b'\\', _, _) => (dx, dy) = (dy, dx),
                (b'/', _, _) => (dx, dy) = (-dy, -dx),
                (b'|', _, 0) | (b'-', 0, _) => {
                    rays.push(((x - dy, y - dx), (-dy, -dx)));
                    (dx, dy) = (dy, dx);
                }
                _ => {}
            }

            (x, y) = (x + dx, y + dy)
        }
    }

    seen.len()
}

Here's the code I wrote for both parts. I'm pretty happy with how concise the reflection logic is, just one short match.

For part two I literally just did this for every possible case. It runs in about 350ms, which is way more than I'm usually happy with; which makes me think that there's some clever way to store sub-results of rays to share calculations between calls. I'm pretty certain there's tons of repeats there.

Maybe store the energized count for every reflector tile+direction combination? And then every time we run into a reflector of some kind, we might already have a cached result. Would have to do some work to avoid double-counting and the like, though. Should be possible.

Unfortunately, that's all hypothetical for me, for I have a train to catch, lmao.